//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"
#include "CBasicDirectory.h"
#define IncrementDllLockCount() do {} while(0)
#define DecrementDllLockCount() do {} while(0)
#include "_CKtfsDirectory.cpp"

#include "CBasicFile.h"
#include "CDirStrEnumerator.h"
#include "FileOps.h"

#include <stdio.h>
#include <stdlib.h>
#include <misc.h>
#include <ChangeNotifier.h>

CKtfsDirectory::~CKtfsDirectory()
{
    if (m_pCFatFile) {
        m_pCFatFile->Release();
    }
}

ECode CKtfsDirectory::Sync()
{
    Lock();
    m_pCFatFile->pFatDir->Syncn(m_pCFatFile->f_node);
    Unlock();
    return NOERROR;
}

ECode CKtfsDirectory::GetType(
    /* [out] */ FileType * pType)
{
    ECode ec;
    Lock();
    ec = FGetType(m_pCFatFile->f_node, pType);
    Unlock();
    return ec;
}

ECode CKtfsDirectory::GetAccessTime(
    /* [out] */ Int32 * pTime)
{
    ECode ec;

    Lock();
    ec = FGetAccessTime(m_pCFatFile, pTime);
    Unlock();
    return ec;
}

ECode CKtfsDirectory::SetAccessTime(
    /* [in] */ Int32 time)
{
    ECode ec;
    Lock();
    ec = FSetAccessTime(m_pCFatFile, time);
    Unlock();
    return ec;
}

ECode CKtfsDirectory::GetModificationTime(
    /* [out] */ Int32 * pTime)
{
    ECode ec;
    Lock();
    ec = FGetModificationTime(m_pCFatFile, pTime);
    Unlock();
    return ec;
}

ECode CKtfsDirectory::SetModificationTime(
    /* [in] */ Int32 time)
{
    ECode ec;
    Lock();
    ec = FSetModificationTime(m_pCFatFile, time);
    Unlock();
    return ec;
}

ECode CKtfsDirectory::GetCreateTime(
    /* [out] */ Int32 * pTime)
{
    ECode ec;
    Lock();
    ec = FGetCreateTime(m_pCFatFile, pTime);
    Unlock();
    return ec;
}

ECode CKtfsDirectory::CheckAccessMode(
    /* [in] */ FileAccessMode mode)
{
    ECode ec;
    Lock();
    ec = FCheckAccessMode(m_pCFatFile, mode);
    Unlock();
    return ec;
}

ECode CKtfsDirectory::GetAccessMode(
    /* [out] */ FileAccessMode * pMode)
{
    ECode ec;
    Lock();
    ec = FGetAccessMode(m_pCFatFile, pMode);
    Unlock();
    return ec;
}

ECode CKtfsDirectory::SetAccessMode(
    /* [in] */ FileAccessMode mode)
{
    ECode ec;
    Lock();
    ec = FSetAccessMode(m_pCFatFile, mode);
    Unlock();
    return ec;
}

ECode CKtfsDirectory::GetShareMode(
    /* [out] */ FileShareMode * pMode)
{
    return E_NOT_IMPLEMENTED;
}

ECode CKtfsDirectory::SetShareMode(
    /* [in] */ FileShareMode mode)
{
    return E_NOT_IMPLEMENTED;
}

ECode CKtfsDirectory::Lookup(
    /* [in] */ WString wstrPath,
    /* [out] */ IBasicFile * * piBasicFile)
{
    ECode ec;
    CKtfsDirectory *pBasicDir = NULL;
    CKtfsFile *pBasicFile = NULL;
    CFatFile* pFatFile = NULL;
    IBasicFile *pIbf = NULL;
    wchar_t *pFileName = NULL;
    struct node *pNode = NULL;

    if (wstrPath.IsNullOrEmpty() || (wcslen(wstrPath) >= FULL_PATH_LEN)
        || (NULL == piBasicFile)) {
        return E_INVALID_ARGUMENT;
    }

    ec = ParseRelativePath((const wchar_t *)wstrPath, &pBasicDir, &pFileName);
    if (FAILED(ec))
        return ec;

    Lock();

    pNode =
        m_pCFatFile->pFatDir->dir_look(pBasicDir->m_pCFatFile->f_node,
                        pFileName);

    Unlock();

    pBasicDir->Release();
    pBasicDir = NULL;

    if (IS_ERR(pNode)) {
        return ErrorToECODE(PTR_ERR(pNode), "CKtfsDirectory::Lookup");
    }

    pFatFile = new CFatFile;
    pFatFile ->pFatDir = m_pCFatFile->pFatDir;
    pFatFile ->pFatDir->AddRef();
    pFatFile ->f_node = pNode;

    switch (pNode->n_type) {
        case T_DIR:
            pBasicDir = new CKtfsDirectory(pFatFile);
            pIbf = IBasicFile::Probe((_IInterface*)pBasicDir);
            if (!pIbf) {
                DPRINTF(("Lookup QI failed. ec = 0x%08x\n", ec));
                return E_NO_INTERFACE;
            }
            pIbf->AddRef();
            pFatFile->f_perm = DEFAULT_PERM;
            break;
        case T_FILE:
            pBasicFile = new CKtfsFile(pFatFile);
            pIbf = pBasicFile;
            pIbf->AddRef();
            break;
        case T_SYM:
            pBasicFile = new CKtfsFile(pFatFile);
            pIbf = pBasicFile;
            pIbf->AddRef();
            break;
        default:
            DPRINTF(("ERROR:Unkonwn file type = 0x%08x!\n", pNode->n_type));
            return E_FS_IO_ERROR;
    }

    *piBasicFile = pIbf;
    return NOERROR;
}

ECode CKtfsDirectory::CreateFile(
    /* [in] */ WString wstrPath,
    /* [in] */ FileAccessMode cmode,
    /* [in] */ Boolean bExclusive,
    /* [out] */ IBasicFile * * piBasicFile)
{
    CKtfsFile *pCKtfsFile = NULL;
    IBasicFile *pIBasicFile = NULL;
    struct node *pNewFileNode = NULL;
    struct node *pNode = NULL;
    class CFatFile *pFatFile = NULL;
    UInt32 uAttr = 0;
    wchar_t *pChPathName = NULL;
    CKtfsDirectory *pDir = NULL;
    ECode ec;

    assert(IsValidFatFile(m_pCFatFile));

    if (wstrPath.IsNullOrEmpty() || (wcslen(wstrPath) >= FULL_PATH_LEN)
        || (NULL == piBasicFile)) {
        return E_INVALID_ARGUMENT;
    }

    uAttr |= ACC_CREATE;

    if (bExclusive) {
        uAttr |= ACC_EXEC;
    }

    if (cmode == FileAccessMode_Read) {
        uAttr |= ACC_READONLY;
    }
    else if (cmode == FileAccessMode_ReadWrite) {
        uAttr |= DEFAULT_PERM;
    }
    else if (cmode == FileAccessMode_Write) {
        uAttr |= ACC_WRITE;
    }
    else {
        DPRINTF(("Unknown file mode.\n"));
        return E_INVALID_ARGUMENT;
    }

    ec = ParseRelativePath((const wchar_t *)wstrPath, &pDir, &pChPathName);
    if (FAILED(ec))
        return ec;

    Lock();
    pNode = m_pCFatFile->pFatDir->dir_look(pDir->m_pCFatFile->f_node,
                    pChPathName);
    if (!IS_ERR(pNode)) {
        if (bExclusive) {
            m_pCFatFile->pFatDir->deref_node(pNode);
            Unlock();
            *piBasicFile = NULL;
            pDir->Release();
            DPRINTF(("WARNING: The file has been existed!\n"));
            return ErrorToECODE(EEXIST, "CKtfsDirectory::CreateFile");
        }
        else {
            if (pNode->n_type == T_DIR) {
                m_pCFatFile->pFatDir->deref_node(pNode);
                Unlock();
                *piBasicFile = NULL;
                pDir->Release();
                return ErrorToECODE(EISDIR, "CKtfsDirectory::CreateFile");
            }
            //now, allfile can read
            if ((pNode->n_mode & ACC_WRITE)
                    && (FileAccessMode_Read == cmode)) {
                pFatFile = new CFatFile;
                pFatFile->pFatDir = m_pCFatFile->pFatDir;
                pFatFile->pFatDir->AddRef();
                pFatFile->f_node = pNode;
                pFatFile->f_pos = 0;

                pFatFile->pFatDir->dir_readonly(pFatFile, 1);
                pFatFile->pFatDir->sync();
                Unlock();
                goto EXIT;
            }
            else if (!(pNode->n_mode & ACC_WRITE)
                && (cmode != FileAccessMode_Read)) {
                m_pCFatFile->pFatDir->deref_node(pNode);
                Unlock();
                *piBasicFile = NULL;
                pDir->Release();
                return E_FS_NO_PERMISSION;
            }
            else {
                Unlock();
                pNewFileNode = pNode;
                goto EXIT1;
            }
        }
    }
    pNewFileNode = m_pCFatFile->pFatDir-> \
                   dir_newfile(pDir->m_pCFatFile, pChPathName, uAttr);
    /* If failed, dir_newfile will return error code */
    if (IS_ERR(pNewFileNode)) {
        Unlock();
        pDir->Release();
        return ErrorToECODE(PTR_ERR(pNewFileNode),
                        "CKtfsDirectory::CreateFile");
    }

    m_pCFatFile->pFatDir->sync();
    Unlock();
EXIT1:
    pFatFile = new CFatFile;
//    pFatFile ->AddRef();
    pFatFile->pFatDir = m_pCFatFile->pFatDir;
    pFatFile->pFatDir->AddRef();
    pFatFile ->f_node = pNewFileNode;
    pFatFile ->f_pos = 0;
EXIT:
    pCKtfsFile = new CKtfsFile(pFatFile);
    pIBasicFile = pCKtfsFile;
    pIBasicFile->AddRef();

    *piBasicFile = pIBasicFile;
    pDir->Release();
    return NOERROR;
}

ECode CKtfsDirectory::DeleteFile(
    /* [in] */ WString wstrPath)
{
    int nError;
    struct node *pNode = NULL;
    ECode ec;
    CKtfsDirectory *pDir = NULL;
    wchar_t *pFileName = NULL;

    if (wstrPath.IsNullOrEmpty() || (wcslen(wstrPath) >= FULL_PATH_LEN)) {
        return E_INVALID_ARGUMENT;
    }

    ec = ParseRelativePath((const wchar_t *)wstrPath, &pDir, &pFileName);
    if (FAILED(ec))
        return ec;

    assert(IsValidFatFile(pDir->m_pCFatFile));
    Lock();
    pNode = m_pCFatFile->pFatDir->dir_look(pDir->m_pCFatFile->f_node,
                    (wchar_t *)pFileName);
    if (IS_ERR(pNode)) {
        Unlock();
        pDir->Release();
        return ErrorToECODE(PTR_ERR(pNode), "CKtfsDirectory::DeleteFile");
    }
    Ktfs_Lock(&pNode->n_lock);
    ASSERT_DEBUG(pNode->n_refs > 0, "n_refs <= 0");

    if (pNode->n_type == T_DIR) {
        Ktfs_Unlock(&pNode->n_lock);
        m_pCFatFile->pFatDir->deref_node(pNode);
        Unlock();
        pDir->Release();
        return ErrorToECODE(EISDIR, "CKtfsDirectory::DeleteFile");
    }

    nError = m_pCFatFile->pFatDir->fatfs_remove(pDir->m_pCFatFile, pNode);
    pDir->Release();
    Ktfs_Unlock(&pNode->n_lock);
    m_pCFatFile->pFatDir->deref_node(pNode);
    m_pCFatFile->pFatDir->sync();

    Unlock();

    return ErrorToECODE(nError, "CKtfsDirectory::DeleteFile");
}

ECode CKtfsDirectory::Rename(
    /* [in] */ WString wstrSrcPath,
    /* [in] */ IBasicDirectory * pNewDir,
    /* [in] */ WString wstrDestPath)
{
    wchar_t *pSrcName, *pDestName, *pSrcPath, *pDestPath;
    CKtfsDirectory *pSrcDir = NULL;
    CKtfsDirectory *pDestDir = NULL;
    ECode ec;
    CFatFile *pNewFatFile = NULL;
    int nError = 0;

    if (wstrSrcPath.IsNullOrEmpty()
        || (wcslen(wstrSrcPath) >= FULL_PATH_LEN)
        || wstrDestPath.IsNullOrEmpty()
        || (wcslen(wstrDestPath) >= FULL_PATH_LEN)
        || (NULL == pNewDir)) {
        return E_INVALID_ARGUMENT;
    }

    pSrcPath = (wchar_t *)malloc(sizeof(wchar_t) * (wcslen(wstrSrcPath) + 1));
    if (NULL == pSrcPath) {
        return E_OUT_OF_MEMORY;
    }
    pDestPath =
        (wchar_t *)malloc(sizeof(wchar_t) * (wcslen(wstrDestPath) + 1));
    if (NULL == pDestPath) {
        free(pSrcPath);
        return E_OUT_OF_MEMORY;
    }

    wcscpy(pSrcPath, (const wchar_t *)wstrSrcPath);
    wcscpy(pDestPath, (const wchar_t *)wstrDestPath);

    /*
     * Wipe all '.' and ' ' at end of filename
     */
    this->m_pCFatFile->pFatDir->dir_treatFileName(pSrcPath);
    this->m_pCFatFile->pFatDir->dir_treatFileName(pDestPath);

    ec = ParseRelativePath(pSrcPath, &pSrcDir, &pSrcName);
    if (FAILED(ec)) {
        goto Exit;
    }

    ec = ((CKtfsDirectory *)pNewDir)->ParseRelativePath(pDestPath, &pDestDir,
                    &pDestName);
    if (FAILED(ec)) {
        goto Exit;
    }

    pNewFatFile = ((CKtfsDirectory *)pDestDir)->m_pCFatFile;

    assert(IsValidFatFile(pNewFatFile));
    assert(IsValidFatFile(pSrcDir->m_pCFatFile));

    if (pNewFatFile->f_node == pSrcDir->m_pCFatFile->f_node) {
        if (0 == wcscmp(pSrcName, pDestName)) {
            ec = NOERROR;
            goto Exit;
        }
        Lock();
    }
    else {
        Lock();
        Ktfs_Lock(&pNewFatFile->f_node->n_lock);
    }

    nError = m_pCFatFile->pFatDir->fatfs_rename( \
                            pSrcDir->m_pCFatFile, pSrcName,
                            pNewFatFile, pDestName);

    if (pNewFatFile->f_node == pSrcDir->m_pCFatFile->f_node) {
        Unlock();
    }
    else {
        Unlock();
        Ktfs_Unlock(&pNewFatFile->f_node->n_lock);
    }

    ec = ErrorToECODE(nError, "CKtfsDirectory::Rename");

Exit:
    if (pSrcPath) free(pSrcPath);
    if (pDestPath) free(pDestPath);
    if (pSrcDir) pSrcDir->Release();
    if (pDestDir) pDestDir->Release();
    return ec;
}

ECode CKtfsDirectory::CreateDirectory(
    /* [in] */ WString wstrPath,
    /* [in] */ FileAccessMode mode)
{
    struct node *pNode = NULL;
    UInt32 uAttr = 0;
    CKtfsDirectory *pDir = NULL;
    wchar_t *pFileName = NULL;
    ECode ec;

    if (wstrPath.IsNullOrEmpty() || (wcslen(wstrPath) >= FULL_PATH_LEN)) {
        return E_INVALID_ARGUMENT;
    }

    uAttr |= ACC_CREATE | ACC_DIR;
    if (mode == FileAccessMode_Read) {
        uAttr |= ACC_READONLY;
    }
    else if (mode == FileAccessMode_ReadWrite) {
        uAttr |= DEFAULT_PERM;
    }
    else if (mode == FileAccessMode_Write) {
        uAttr |= ACC_WRITE;
    }
    else {
        DPRINTF(("Unknown Directory mode.\n"));
        return E_INVALID_ARGUMENT;
    }

    ec = ParseRelativePath((const wchar_t *)wstrPath, &pDir, &pFileName);
    if (FAILED(ec))
        return ec;

    assert(IsValidFatFile(pDir->m_pCFatFile));

    Lock();
    pNode = m_pCFatFile->pFatDir->dir_look(pDir->m_pCFatFile->f_node,
                    pFileName);
    if (!IS_ERR(pNode)) {
        m_pCFatFile->pFatDir->deref_node(pNode);
        pDir->Release();
        Unlock();
        DPRINTF(("WARNING: The directory %S has been existed!\n", pFileName));
        return ErrorToECODE(EEXIST, "CKtfsDirectory::CreateDirectory");
    }

    pNode = m_pCFatFile->pFatDir-> \
                   dir_newfile(pDir->m_pCFatFile, pFileName, uAttr);
    pDir->Release();
    /* If failed, dir_newfile will return error code */
    if (IS_ERR(pNode)) {
        Unlock();
        return ErrorToECODE(PTR_ERR(pNode), "CKtfsDirectory::CreateDirectory");
    }

    m_pCFatFile->pFatDir->deref_node(pNode);

    m_pCFatFile->pFatDir->sync();
    Unlock();

    return NOERROR;
}

ECode CKtfsDirectory::DeleteDirectory(
    /* [in] */ WString wstrPath)
{
    int nError;
    struct node *pNode = NULL;
    CKtfsDirectory *pDir = NULL;
    wchar_t *pFileName = NULL;
    ECode ec;

    if (wstrPath.IsNullOrEmpty() || (wcslen(wstrPath) >= FULL_PATH_LEN)) {
        return E_INVALID_ARGUMENT;
    }

    ec = ParseRelativePath((const wchar_t *)wstrPath, &pDir, &pFileName);

    if (FAILED(ec))
        return ec;

    assert(IsValidFatFile(pDir->m_pCFatFile));

    Lock();
    pNode = m_pCFatFile->pFatDir->dir_look(pDir->m_pCFatFile->f_node,
                    pFileName);
    if (IS_ERR(pNode)) {
        pDir->Release();
        Unlock();
        return ErrorToECODE(PTR_ERR(pNode), "CKtfsDirectory::DeleteDirectory");
    }
    Ktfs_Lock(&pNode->n_lock);
    if (pNode->n_type != T_DIR) {
        Ktfs_Unlock(&pNode->n_lock);
        m_pCFatFile->pFatDir->deref_node(pNode);
        pDir->Release();
        Unlock();
        return ErrorToECODE(ENOTDIR, "CKtfsDirectory::DeleteDirectory");
    }

    nError = m_pCFatFile->pFatDir->fatfs_remove(pDir->m_pCFatFile, pNode);
    pDir->Release();
    Ktfs_Unlock(&pNode->n_lock);

    m_pCFatFile->pFatDir->deref_node(pNode);
    Unlock();

    return ErrorToECODE(nError, "CKtfsDirectory::DeleteDirectory");
}

ECode CKtfsDirectory::GetDirEntries(
    /* [out] */ IWStringEnumerator * * piEntryNameEnumerator)
{
    ECode ec;
    IWStringEnumerator *pDirEnum;

    if (NULL == piEntryNameEnumerator) {
        return E_INVALID_ARGUMENT;
    }

    if (m_pCFatFile->f_node->n_type != T_DIR) {
        return (ErrorToECODE(ENOTDIR, "CKtfsDirectory::GetDirEntries"));
    }

    pDirEnum = new CKtfsDirStrEnumerator((IBasicDirectory*)this, m_pCFatFile);

    if (NULL == pDirEnum) {
        ec = E_OUT_OF_MEMORY;
        return ec;
    }

    ec = pDirEnum->Reset();
    if (FAILED(ec)) {
        return ec;
    }

    *piEntryNameEnumerator = pDirEnum;
    pDirEnum->AddRef();

    return NOERROR;
}

Boolean CKtfsDirectory::IsValidFatFile(class CFatFile *pCFatFile)
{
    if (NULL == pCFatFile || NULL == pCFatFile->f_node
        || NULL == pCFatFile->pFatDir) {
        return FALSE;
    }
    return TRUE;
}

ECode CKtfsDirectory::AddFileMonitor(
    /* [out] */ IEvent** ppEvent)
{
    if (NULL == ppEvent)
        return E_INVALID_ARGUMENT;

    CChangeNotifier *pNotifier = m_pCFatFile->pFatDir->p_Sharedata->pNotifier;
    int error = 0;

    if (NULL == pNotifier) {
        pNotifier = new CChangeNotifier;
        if (NULL == pNotifier)
            return E_OUT_OF_MEMORY;
    }

    error = pNotifier->AddMonitor(m_pCFatFile->f_node, ppEvent);

    if (error != 0)
        return ErrorToECODE(error, "CKtfsDirectory::AddFileMonitor");
    /* Make sure the node who has been monitored could not be Release. */
    m_pCFatFile->pFatDir->ref_node(m_pCFatFile->f_node);
    m_pCFatFile->pFatDir->p_Sharedata->pNotifier = pNotifier;

    return NOERROR;
}

ECode CKtfsDirectory::RemoveFileMonitor(
    /* [in] */ IEvent * pEvent)
{
    int error = 0;
    CChangeNotifier *pNotifier = m_pCFatFile->pFatDir->p_Sharedata->pNotifier;

    if (NULL == pNotifier) {
        return ErrorToECODE(EIO, "CKtfsDirectory::RemoveFileMonitor");
    }

    if (NULL == pEvent) {
        return E_INVALID_ARGUMENT;
    }

    error = pNotifier->RemoveMonitor(m_pCFatFile->f_node, pEvent);
    if (error != 0)
        return ErrorToECODE(error, "CKtfsDirectory::RemoveFileMonitor");

    error = m_pCFatFile->pFatDir->deref_node(m_pCFatFile->f_node);

    if (error != 0)
        return ErrorToECODE(error, "CKtfsDirectory::RemoveFileMonitor");

    return NOERROR;
}

inline void CKtfsDirectory::Lock()
{
    Ktfs_Lock(&this->m_pCFatFile->f_node->n_lock);
}

inline void CKtfsDirectory::Unlock()
{
    Ktfs_Unlock(&this->m_pCFatFile->f_node->n_lock);
}

ECode CKtfsDirectory::ParseRelativePath(const wchar_t *pRelativePath,
    CKtfsDirectory **ppRetDirectory,
    wchar_t **ppLastName)
{
    WStringBuf_<MAX_FILE_NAME> esbStr;
    wchar_t wszName[MAX_FILE_NAME];
    wchar_t *p = NULL;
    IBasicDirectory *pDir = this;
    IBasicFile *pTempDir = NULL;
    int i = 0;
    ECode ec;

    assert(pRelativePath && ppRetDirectory && ppLastName);
    if (*pRelativePath == L'\0')
        return E_INVALID_ARGUMENT;

    pDir->AddRef();
    *ppLastName = (wchar_t *)pRelativePath;

    for (p = *ppLastName; *p != L'\0'; p++) {
        if (IS_PATH_SEPARATOR(*p)) {
            wszName[i] = L'\0';
            i = 0;
            ec = pDir->Lookup(wszName, &pTempDir);
            pDir->Release();
            if (FAILED(ec)) return ec;
            pDir = IBasicDirectory::Probe(pTempDir); if (pDir) pDir->AddRef();
            pTempDir->Release();
            if (!pDir) return E_NO_INTERFACE;

            *ppLastName = p + 1;
        }
        else {
            wszName[i++] = *p;
            if (i >= MAX_FILE_NAME) {
                pDir->Release();
                return E_INVALID_ARGUMENT;
            }
        }
    }

    *ppRetDirectory = (CKtfsDirectory *)pDir;

    return NOERROR;
}
